/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package rules.generator;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import matching.ClassMatch;
import matching.DataPropertyMatch;
import matching.Match;
import matching.Matching;
import matching.ObjectPropertyMatch;
import matching.path.PairOfPath;
import matching.path.Path;
import matching.path.PropertyPair;
import matching.triple.ClassTriple;
import matching.triple.PropertyTriple;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.semanticweb.owl.model.OWLDescription;
import org.semanticweb.owl.model.OWLObjectProperty;
import rules.tree.Body;
import rules.tree.BuiltinAtom;
import rules.tree.ClassAtom;
import rules.tree.Context;
import rules.tree.Function;
import rules.tree.Head;
import rules.tree.Mapping;
import rules.tree.PropertyAtom;
import rules.tree.Restriction;
import rules.tree.Rule;
import rules.tree.Var;


/**
 *
 * @author livia
 */
public class Generator {
    private Matching matching;
    public Mapping sm;
    private int count;
    
    public Generator(Matching matchings) {
        matching = matchings;
        sm = new Mapping();
        count = 0;
    }

    /**
     * Case 1: If lo:v1 and do:v2 are classes and do:r2 is the top class , then
	 * M = {do:v2(x)  lo:v1(x), lo:r1(x)} uniao
     * {do:s(x)  lo:v1(x), lo:r1(x), for each superclass s of do:v2}
     * */
    public ArrayList<Rule> insertRuleCase1(ClassMatch mat){
        ArrayList<Rule> rules = new ArrayList<Rule>();
        //do:v2(x)<= lo:v1(x),lo:r1(x)
        Rule rule = new Rule();
        rule.setID("r"+(++count));
        //do:v2(x)
        Head h = new Head();
        ClassAtom a = new ClassAtom((String) mat.getTarget().getElement(), new Var("x"));
        h.setAtom(a);
        rule.setHead(h);
        //lo:v1(x),lo:r1(x)
        //lo:v1(x)
        Body b = new Body();
        rule.setBody(b);
            a = new ClassAtom((String) mat.getSource().getElement(),  new Var("x"));
        b.addAtom(a);
        //lo:r1(x)
        if(mat.source.getRestriction()!=null){
            Context ct = new Context();
            matching.Restriction r = mat.getSource().getRestriction();
            PropertyAtom pa = new PropertyAtom(r.getAtributte().toString(), new Var("y"),  new Var("z"));
            BuiltinAtom ba = new BuiltinAtom("swrlb:equal",  new Var("z") , r.getValue());
            Restriction rmp = new Restriction(true);
            rmp.setBuiltinAtom(ba);
            rmp.setPropAtom(pa);
            ct.setRestrictionBody(rmp);
            b.setContext(ct);
        }
        rules.add(rule);
        // for each superclass s of v2
        ClassTriple target= (ClassTriple)mat.target;
        for (OWLDescription c :target.element.getSuperClasses(matching.target.owl)) {
            //do:s(x)<= lo:v1(x),lo:r1(x)
            rule = new Rule();
            rule.setID("r"+(++count));
            h = new Head();
            a = new ClassAtom(c.toString(), new Var("x"));
            h.setAtom(a);
            rule.setHead(h);
            //lo:v1(x)
            b = new Body();
            rule.setBody(b);
            a = new ClassAtom((String) mat.getSource().getElement(),  new Var("x"));
            b.addAtom(a);
            //lo:r1(x)
            if(mat.source.getRestriction()!=null){
                Context ct = new Context();
                matching.Restriction r = mat.getSource().getRestriction();
                PropertyAtom pa = new PropertyAtom(r.getAtributte().toString(),new Var("x"), new Var("z"));
                BuiltinAtom ba = new BuiltinAtom("swrlb:equal",  new Var("z") , r.getValue());
                Restriction rmp = new Restriction(true);
                rmp.setBuiltinAtom(ba);
                rmp.setPropAtom(pa);
                ct.setRestrictionBody(rmp);
                b.setContext(ct);
            }
            rules.add(rule);
	}
        return rules;
    }

    /*
     * Case 2: If lo:v1 and do:v2 are classes and do:r2 is a restriction of the form do:p2 = do:c2,
     * where do:p2 is a datatype property and do:c2 is a constant, then
	 * M = {do:v2(x) <- lo:v1(x), lo:r1(x)} uniao
     * {do:s(x) <- lo:v1(x), lo:r1(x), for each superclass s of do:v2}
     * {do:p2(x, do:c2) <- lo:v1(x), lo:r1(x)}
     */
    public ArrayList<Rule> insertRuleCase2(ClassMatch mat){
        ArrayList<Rule> rules = new ArrayList<Rule>();
        //do:v2(x)<= lo:v1(x),lo:r1(x)
        rules.addAll(insertRuleCase1(mat));
        //do:p2(x, do:c2) <- lo:v1(x), lo:r1(x)
        Rule rule = new Rule();
        rule.setID("r"+(++count));
        //do:p2(x, do:c2)
        Head h = new Head();
        PropertyAtom a = new PropertyAtom(mat.target.getRestriction().getAtributte().toString(), new Var("x"), mat.target.getRestriction().getValue());
        h.setAtom(a);
        rule.setHead(h);
       //lo:v1(x),lo:r1(x)
        //lo:v1(x)
        Body b = new Body();
        rule.setBody(b);
        ClassAtom    ca = new ClassAtom((String) mat.getSource().getElement(),  new Var("x"));
        b.addAtom(ca);
        //lo:r1(x)
        if(mat.source.getRestriction()!=null){
            Context ct = new Context();
            matching.Restriction r = mat.getSource().getRestriction();
            PropertyAtom pa = new PropertyAtom(r.getAtributte().toString(), new Var("x"),  new Var("z"));
            BuiltinAtom ba = new BuiltinAtom("swrlb:equal",  new Var("z") , r.getValue());
            Restriction rmp = new Restriction(true);
            rmp.setBuiltinAtom(ba);
            rmp.setPropAtom(pa);
            ct.setRestrictionBody(rmp);
            b.setContext(ct);
        }
        rules.add(rule);
        // for each superclass s of v2
        ClassTriple target= (ClassTriple)mat.target;
        for (OWLDescription c :target.element.getSuperClasses(matching.target.owl)) {
            count++;
            //do:s(x)<= lo:v1(x),lo:r1(x)
            rule.setID("r"+count);
            rule = new Rule();
            h = new Head();
            ca = new ClassAtom(c.toString(), new Var("x"));
            h.setAtom(ca);
            rule.setHead(h);
            //lo:v1(x)
            b = new Body();
            rule.setBody(b);
            ca = new ClassAtom((String) mat.getSource().getElement(),  new Var("x"));
            b.addAtom(ca);
            //lo:r1(x)
            if(mat.source.getRestriction()!=null){
                Context ct = new Context();
                matching.Restriction r = mat.getSource().getRestriction();
                PropertyAtom pa = new PropertyAtom(r.getAtributte().toString(),  new Var("x"), new Var("z"));
                BuiltinAtom ba = new BuiltinAtom("swrlb:equal",  new Var("x") , r.getValue());
                Restriction rmp = new Restriction(true);
                rmp.setBuiltinAtom(ba);
                rmp.setPropAtom(pa);
                ct.setRestrictionBody(rmp);
                b.setContext(ct);
            }
            rules.add(rule);
	}
        return rules;
    }

    /**
     * Case 3: If lo:v1 and do:v2 are both datatype properties (or both
	 * object properties) such that (lo:v1 C= do:v2), then
 	 * M = {do:v2(x, y) <= lo:v1(x, y), lo:e1(x), lo:r1(x)}
     */
   public Rule insertRuleCase3(Match mat){
        Rule rule = new Rule();
        rule.setID("r"+(++count));
        //do:v2(x, y) <= lo:v1(x, y), lo:e1(x), lo:r1(x)
        //do:v2(x, y)
        Head h = new Head();
        PropertyAtom a = new PropertyAtom(mat.target.getElement().toString(), new Var("x"), new Var("y"));
        h.setAtom(a);
        rule.setHead(h);
        //lo:v1(x, y)
        rule.setBody(getBodyCase3_5(mat));
        return rule;
    }


   /**
    * Case 4: lo:v1 and do:v2 are both Datatype Properties (or both Object Properties) and there is a property path
    * teta = lo:pk1(x,x1), lo:pk2(x1,x2), lo:pk3(x2,x3),...,lo:pkm(xm-1,z),in source ontology, then
    * M = {do:v2(x,y)<= lo:pk1(x,x1), lo:pk2(x1,x2), lo:pk3(x2,x3),...,lo:pkm(xm-1,z),lo:v1(z,y),lo:e1(z),lo:r1(z)}
    * @param mat
    */
   public Rule insertRuleCase4(Match mat){
        Rule rule = new Rule();
        rule.setID("r"+(++count));
        //do:v2(x,y)<= lo:pk1(x,x1), lo:pk2(x1,x2), lo:pk3(x2,x3),...,lo:pkm(xm-1,z),lo:v1(z,y),lo:e1(z),lo:r1(z)
        //do:v2(x, y)
        Head h = new Head();
        PropertyAtom a = new PropertyAtom(mat.target.getElement().toString(), new Var("x"), new Var("y"));
        h.setAtom(a);
        rule.setHead(h);
        rule.setBody(getBodyCase4_6(mat));
        return rule;
    }

    /**
    * Case 5: lo:v1 and do:v2 are both Datatype Properties (or both Object Properties) and there is a property path
    * teta = do:pk1(x,x1), do:pk2(x1,x2), do:pk3(x2,x3),...,do:pkm(xm-1,z),in source ontology, then
    * M = {do:v2(f(y),y)<= lo:v1(x,y),lo:e1(x),lo:r1(x)}
    * M = {do:e2(f(y))<= lo:v1(x,y),lo:e1(x),lo:r1(x)}
    * M = {do:op2(x,f(y))<= lo:v1(x,y),lo:e1(x),lo:r1(x)}
    * @param mat
    */

   //TODO adicionar elemento no arquivo de matching para identificar a propriedade inversa funcional
   private ArrayList<Rule> insertRuleCase5(Match mat){
        ArrayList<Rule> ret = new ArrayList<Rule>();
        Rule rule = new Rule();
        rule.setID("r"+(++count));
        //do:v2(f(y),y)<= lo:v1(x,y),lo:e1(x),lo:r1(x)
        //do:v2(f(y),y)
        Head h = new Head();
        PropertyAtom a = new PropertyAtom(mat.target.getElement().toString(), new Function("f", new Var("y")), new Var("y"));
        h.setAtom(a);
        rule.setHead(h);
        //lo:v1(x,y),lo:e1(x),lo:r1(x)
        rule.setBody(getBodyCase3_5(mat));
        ret.add(rule);

        //do:e2(f(y))<= lo:v1(x,y),lo:e1(x),lo:r1(x)
        rule = new Rule();
        rule.setID("r"+(++count));
        //do:e2(f(y))
        h = new Head();
        ClassAtom ca = new ClassAtom(mat.target.getContext().toString(), new Function("f", new Var("y")));
        h.setAtom(ca);
        rule.setHead(h);
        //lo:v1(x,y),lo:e1(x),lo:r1(x)            
        rule.setBody(getBodyCase3_5(mat));
        ret.add(rule);

        //do:op2(x,f(y))<= lo:v1(x,y),lo:e1(x),lo:r1(x)
        rule = new Rule();
        rule.setID("r"+(++count));
        //do:op2(x,f(y))
        h = new Head();
        PairOfPath p = ((PropertyTriple)mat.target).getPath().getNodes().getLast();
        OWLObjectProperty op = ((PropertyPair)p).getProperty();
        PropertyAtom pa = new PropertyAtom(op.toString(), new Var("x") ,new Function("f", new Var("y")));
        h.setAtom(pa);
        rule.setHead(h);
        //lo:v1(x,y),lo:e1(x),lo:r1(x)
        rule.setBody(getBodyCase3_5(mat));
        ret.add(rule);

        return ret;
    }

    //lo:v1(x,y),lo:e1(x),lo:r1(x)
   private Body getBodyCase3_5(Match mat){
         //lo:v1(x,y),lo:e1(x),lo:r1(x)
        Body b = new Body();
        //lo:v1(x,y)
        PropertyAtom a  = new PropertyAtom(mat.getSource().getElement().toString(), new Var("x"), new Var("y"));
        b.addAtom(a);
        //lo:e1(x)
        Context c = new Context();
        b.addAtom(new ClassAtom(mat.source.getContext().toString(), new Var("x")));
        //lo:r1(x)
        if(mat.source.getRestriction()!=null){
            c = new Context();
            matching.Restriction r = mat.getSource().getRestriction();
            PropertyAtom pa = new PropertyAtom(r.getAtributte().toString(), new Var("x"),  new Var("z"));
            BuiltinAtom ba = new BuiltinAtom("swrlb:equal",  new Var("z") , r.getValue());
            Restriction rmp = new Restriction(true);
            rmp.setBuiltinAtom(ba);
            rmp.setPropAtom(pa);
            c.setRestrictionBody(rmp);
            b.setContext(c);
        }
        return b;
    }

    /**
     * Case 6:if lo:v1 and lo:v2 both are datatype properties (or both are object properties) and there
     * is a property path teta in the local ontology (LO) and also a property path teta' in the doamin ontology
     * (DO) (as this case is a combination of cases 4 and 5), then M contains the rules:
     * do:v2(f(y),y) <= lo:pk1(x,x1), lo:pk2(x1,x2), lo:pk3(x2,x3),...,lo:pkm(xm-1,z),lo:v1(z,y),lo:e1(z),lo:r1(z)
     * do:e2(f(y)) <= lo:pk1(x,x1), lo:pk2(x1,x2), lo:pk3(x2,x3),...,lo:pkm(xm-1,z),lo:v1(z,y),lo:e1(z),lo:r1(z)
     * do:op2(x, f(y)) <= lo:pk1(x,x1), lo:pk2(x1,x2), lo:pk3(x2,x3),...,lo:pkm(xm-1,z),lo:v1(z,y),lo:e1(z),lo:r1(z)
     * @param mat
     * @return
     */
    private ArrayList<Rule> insertRuleCase6(Match mat){
        ArrayList<Rule> ret = new ArrayList<Rule>();
        Rule rule = new Rule();
        rule.setID("r"+(++count));

        //do:v2(f(y),y)<= lo:v1(x,y),lo:e1(x),lo:r1(x)
        //do:v2(f(y),y)
        Head h = new Head();
        PropertyAtom a = new PropertyAtom(mat.target.getElement().toString(), new Var("y"), new Function("f", new Var("y")));
        h.setAtom(a);
        rule.setHead(h);
        //lo:v1(x,y),lo:e1(x),lo:r1(x)
        rule.setBody(getBodyCase4_6(mat));
        ret.add(rule);

        // do:e2(f(y)) <= lo:pk1(x,x1), lo:pk2(x1,x2), lo:pk3(x2,x3),...,lo:pkm(xm-1,z),lo:v1(z,y),lo:e1(z),lo:r1(z)
        rule = new Rule();
        rule.setID("r"+(++count));
        //do:e2(f(y))
        h = new Head();
        ClassAtom ca = new ClassAtom(mat.target.getContext().toString(), new Function("f", new Var("y")));
        h.setAtom(ca);
        rule.setHead(h);
        //lo:v1(x,y),lo:e1(x),lo:r1(x)
        rule.setBody(getBodyCase4_6(mat));

        //do:op2(x,f(y))<= lo:v1(x,y),lo:e1(x),lo:r1(x)
        rule = new Rule();
        rule.setID("r"+(++count));
        //do:op2(x,f(y))
        h = new Head();
        PairOfPath p = ((PropertyTriple)mat.target).getPath().getNodes().getLast();
        OWLObjectProperty op = ((PropertyPair)p).getProperty();
        PropertyAtom pa = new PropertyAtom(op.toString(), new Var("x") ,new Function("f", new Var("y")));
        h.setAtom(pa);
        rule.setHead(h);
        //lo:v1(x,y),lo:e1(x),lo:r1(x)
        rule.setBody(getBodyCase4_6(mat));
        ret.add(rule);
        return ret;
    }

    //lo:pk1(x,x1), lo:pk2(x1,x2), lo:pk3(x2,x3),...,lo:pkm(xm-1,z),lo:v1(z,y),lo:e1(z),lo:r1(z)
    private Body getBodyCase4_6(Match mat){        
        //lo:pk1(x,x1), lo:pk2(x1,x2), lo:pk3(x2,x3),...,lo:pkm(xm-1,z)
        Path path = ((PropertyTriple)mat.getSource()).getPath();
        Body b = new Body();
        Iterator<PairOfPath> it = path.getNodes().iterator();
        PropertyAtom a  = new PropertyAtom(((PropertyPair)it.next()).getProperty().toString(), new Var("x"), new Var("x1"));
        b.addAtom(a);
        int i=1;
        while(it.hasNext()){
            PairOfPath p = it.next();
            if(p instanceof PropertyPair){
                a  = new PropertyAtom(((PropertyPair)p).getProperty().toString(), new Var("x"+i), new Var("x"+(++i)));
                b.addAtom(a);
            }
        }
        //lo:v1(z,y)
        a  = new PropertyAtom(mat.getSource().getElement().toString(), new Var("x"+i), new Var("y"));
        b.addAtom(a);
        //lo:e1(x)
        Context c = new Context();
        b.addAtom(new ClassAtom(mat.source.getContext().toString(), new Var("x"+i)));
        //lo:r1(x)
        if(mat.source.getRestriction()!=null){
            c = new Context();
            matching.Restriction r = mat.getSource().getRestriction();
            PropertyAtom pa = new PropertyAtom(r.getAtributte().toString(), new Var("y"),  new Var("z"));
            BuiltinAtom ba = new BuiltinAtom("swrlb:equal",  new Var("z") , r.getValue());
            Restriction rmp = new Restriction(true);
            rmp.setBuiltinAtom(ba);
            rmp.setPropAtom(pa);
            c.setRestrictionBody(rmp);
            b.setContext(c);
        }
        return b;
    }

    public void generateRules(){
        sm.setSourceLocation(matching.source.uriLocation.toString());
        sm.setTargetLocation(matching.target.uriLocation.toString());
        sm.setSourceURI(matching.source.owl.getURI().toString()+"#");
        sm.setTargetURI(matching.target.owl.getURI().toString()+"#");
        for (Match mat : matching.matchings) {
            if(mat instanceof ClassMatch){
                if(mat.target.getRestriction()==null){
                     for (Rule rule : insertRuleCase1((ClassMatch) mat)) {
                         sm.addRule(rule);
                     }
                }else{
                     for (Rule rule : insertRuleCase2((ClassMatch) mat)) {
                         sm.addRule(rule);
                     }
                }
            }
            else if(mat instanceof DataPropertyMatch || mat instanceof ObjectPropertyMatch){
                if(((PropertyTriple)mat.getSource()).hasPath()&& ((PropertyTriple)mat.getTarget()).hasPath()){
                    for (Rule rule : insertRuleCase6(mat)) {
                        sm.addRule(rule);
                    }
                }else if (((PropertyTriple)mat.getTarget()).hasPath()){
                    for (Rule rule : insertRuleCase5(mat)) {
                        sm.addRule(rule);
                    }
                }else if(((PropertyTriple)mat.getSource()).hasPath()){
                        sm.addRule(insertRuleCase4(mat));
                }else{
                        sm.addRule(insertRuleCase3(mat));
                }
            }

        }
    }

    public void save() throws IOException {
        File f = new File("mapping.xml");
        FileWriter arquivo = new FileWriter(f);
        Format fmt = Format.getPrettyFormat();
        fmt.setIndent( "    " );
        XMLOutputter xout = new XMLOutputter(fmt);
        xout.output(sm.getDoc() , arquivo);
        arquivo.close();
    }

}

